# LockSupport 详解
# LockSupport 的作用
LockSupport 为线程和其他同步类提供了基本的阻塞原语。借助于 Unsafe 类的 API,LockSupport 提供了 park 操作用于阻塞线程,提供了 unpark 操作用于唤醒线程。
park / unpark 底层的原理是“二元信号量”,你可以把它想象成只有一个许可证的 Semaphore,只不过这个信号量在重复执行 unpark 的时候也不会再增加许可证,最多只有一个许可证。
# LockSupport 源码解析
# 核心属性
private static final sun.misc.Unsafe UNSAFE;
// 下面是 Thread 类的成员变量的内存偏移量
private static final long parkBlockerOffset; 
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        // 线程的阻塞者
        parkBlockerOffset = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("parkBlocker"));
        SEED = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSeed"));
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception ex) { throw new Error(ex); }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 构造函数
private LockSupport() {}
LockSupport 作为一个工具类是不能被实例化的,其提供的方法都是 static 的,可以通过类名直接调用。
# 核心函数
LockSupport 的核心函数主要是两种类型:park 和 unpark,而 park 是用来阻塞线程的,unpark 用于唤醒指定线程。
# unpark()
/**
 * 该方法主要有三方面需要注意
 * 
 * 1. 如果指定的线程正在阻塞中,可以调用该方法进行唤醒;
 * 2. 如果指定的线程没有阻塞,那么该方法可以保证该线程下一次调用 park 是不会被阻塞
 * 3. 如果指定的线程还没有启动(没有调用 start 方法),则调用该方法不会有任何作用
 */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
} 
2
3
4
5
6
7
8
9
10
11
# park()
不同于 unpark 只有一个方法,park 有了三种类型的方法:不做任何设置、可以设置 blocker、可以设置超时时间。 park 方法的源码不复杂,这里直接通过表格来对比各个方法的区别会容易理解一些。
| 方法 | 使用描述 | 
|---|---|
| park() | 直接挂起当前线程 | 
| park(Object blocker) | 挂起当前线程,并设置负责挂起当前线程的对象 | 
| parkNanos(Object blocker, long nanos) | 挂起当前线程,并设置负责挂起当前线程的对象以及超时时间(相对时间) | 
| parkUntil(Object blocker, long deadline) | 挂起当前线程,并设置负责挂起当前线程的对象以及超时时间(绝对时间) | 
| parkNanos(long nanos) | 挂起当前线程,并设置超时时间(相对时间) | 
| parkUntil(long deadline) | 挂起当前线程,并设置超时时间(绝对时间) | 
| Object getBlocker(Thread t) | 获取负责阻塞指定线程的对象,如果线程没有阻塞,则返回 null | 
对于调用了 park 方法而阻塞的线程,线程唤醒条件有以下几种:
- 其他线程使用 unpark 方法唤醒了阻塞的线程;
- 阻塞线程被中断;
- 其他一些不合逻辑的原因导致异常醒来;
- 设置的超时时间到了。
对于第三点,源码的注释表示推荐使用 while 循环来判断线程的阻塞条件,可能还需要增加对中断的判断。
while (condition()) { 
    LockSupport.park(this);
}
2
3
# LockSupport 的使用
# 示例一:park 和 unpark 搭配使用
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample {
    /**
     * 通过 unpark 唤醒另一条线程
     */
    private static class UnparkThread extends Thread {
        /**
         * 等待唤醒的目标线程
         */
        private Thread target;
        UnparkThread(Thread target) {
            this.target = target;
        }
        @Override
        public void run() {
            try {
                String threadName = currentThread().getName();
                System.out.println(threadName +  ": before unpark");
                // 睡眠,以保证 park 先执行
                TimeUnit.SECONDS.sleep(1);
                System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
                LockSupport.unpark(target);
                System.out.println(threadName +  ": after unpark");
                // unpark 会将 target 的 blocker 置为 null
                // 这里睡眠 1 秒以保证 target 的 blocker 为 null
                TimeUnit.SECONDS.sleep(1);
                System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        UnparkThread upt = new UnparkThread(Thread.currentThread());
        upt.start();
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName +  ": before park");
        LockSupport.park("blocker of main");
        System.out.println(threadName +  ": after park");
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
运行结果:
Thread-0: before unpark
main: before park
Thread-0: target thread blocker info: blocker of main
Thread-0: after unpark
main: after park
Thread-0: target thread blocker info: null
2
3
4
5
6
第 、 行的打印顺序可能有变化,但是不影响。
# 示例二:通过中断唤醒 park 的线程
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample2 {
    /**
     * 通过中断唤醒另一条线程
     */
    private static class InterruptThread extends Thread {
        /**
         * 等待唤醒的目标线程
         */
        private Thread target;
        InterruptThread(Thread target) {
            this.target = target;
        }
        @Override
        public void run() {
            try {
                String threadName = currentThread().getName();
                System.out.println(threadName + ": before interrupt");
                // 睡眠,以保证 park 先执行
                TimeUnit.SECONDS.sleep(1);
                System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
                // 线程中断,之后 main 线程会被唤醒
                target.interrupt();
                System.out.println(threadName + ": after interrupt");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        InterruptThread it = new InterruptThread(Thread.currentThread());
        it.start();
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName +  ": before park");
        LockSupport.park("blocker of main");
        System.out.println(threadName +  ": after park");
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
运行结果:
Thread-0: before interrupt
main: before park
Thread-0: target thread blocker info: blocker of main
Thread-0: after interrupt
main: after park
Thread-0: target thread blocker info: null
2
3
4
5
6
# 示例三:设置超时,自动唤醒 park 的线程
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample3 {
    public static void main(String[] args) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName +  ": before park");
        LockSupport.parkNanos("blocker of main", TimeUnit.SECONDS.toNanos(1));
        System.out.println(threadName +  ": after park");
    }
}
2
3
4
5
6
7
8
9
10
11
12
运行结果:
main: before park
main: after park
2
# 示例四:先 unpark 再 park,线程不会阻塞
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample4 {
    /**
     * 先调用 unpark
     */
    private static class UnparkFirstThread extends Thread {
        /**
         * 等待唤醒的目标线程
         */
        private Thread target;
        UnparkFirstThread(Thread target) {
            this.target = target;
        }
        @Override
        public void run() {
            String threadName = currentThread().getName();
            System.out.println(threadName + ": before unpark");
            System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
            LockSupport.unpark(target);
            System.out.println(threadName + ": after unpark");
            System.out.println(threadName +  ": target thread blocker info: " + LockSupport.getBlocker(target));
        }
    }
    public static void main(String[] args) throws InterruptedException {
        UnparkFirstThread upf = new UnparkFirstThread(Thread.currentThread());
        upf.start();
        // 睡眠,等待 upf 线程执行完,
        // 保证先对 main 线程执行 unpark 操作
        TimeUnit.SECONDS.sleep(1);
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName +  ": before park");
        LockSupport.park("blocker of main");
        System.out.println(threadName +  ": after park");
    }
}	
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
运行结果:
Thread-0: before unpark
Thread-0: target thread blocker info: null
Thread-0: after unpark
Thread-0: target thread blocker info: null
main: before park
main: after park
2
3
4
5
6
# 示例五:park 不会释放锁资源
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample5 {
    /**
     * 锁获取
     */
    private static class LockThread extends Thread {
        /**
         * 等待唤醒的目标线程
         */
        private Thread target;
        LockThread(Thread target) {
            this.target = target;
        }
        @Override
        public void run() {
            // 请求获得锁
            synchronized (this) {
                System.out.println("before unpark");
                LockSupport.unpark(target);
                System.out.println("after unpark");
            }
        }
    }
    public static void main(String[] args) {
        LockThread lockThread = new LockThread(Thread.currentThread());
        // 将 lockThread 的 start 放在 synchronized 块中调用
        // 保证 lockThread 启动的时候,main 线程已经获得锁
        synchronized (lockThread) {
            lockThread.start();
            System.out.println("before park");
            LockSupport.park();
            System.out.println("after park");
        }
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
运行结果:
before park
打印第 行之后,程序不会继续进行下去,因为 main 线程 park 之后,没有释放 lockThread 锁,所以 LockThread 线程无法获得锁去调用 unpark 方法,从而无法继续执行程序。
# LockSupport.park() & Object.wait() 对比
如果通过 Object 的 await() 和 notify()/notifyAll() 阻塞唤醒线程,需要像下面这么做。
和 park、unpark 不同的是,unpark 调用后再调用 park ,线程是不会阻塞的,但是如果先调用 notify 再调用 wait,则线程会一直阻塞。
import java.util.concurrent.TimeUnit;
/**
 * @author linjinjia
 * @date 2023/7/13 15:19
 */
public class ObjectAwaitExample {
    private static class NotifyThread extends Thread {
        @Override
        public void run() {
            synchronized (this) {
                String threadName = currentThread().getName();
                System.out.println(threadName + ": before notify");
                notify();
                System.out.println(threadName + ": after notify");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        // 先执行 wait 再执行 notify 才是正确顺序
        NotifyThread nt = new NotifyThread();
        nt.setName("nt");
        synchronized (nt) {
            nt.start();
            System.out.println(threadName + ": before wait");
            nt.wait();
            System.out.println(threadName + ": after wait");
        }
        // 分割线
        TimeUnit.SECONDS.sleep(1);
        System.out.println("==================================");
        // 如果先执行 notify 再调用 wait,则主线程会一直阻塞
        NotifyThread nt2 = new NotifyThread();
        nt2.setName("nt2");
        nt2.start();
        // main 线程睡眠 1 秒,以保证 nt2 线程执行完成
        // 保证 notify 已经执行完成
        TimeUnit.SECONDS.sleep(1);
        synchronized (nt2) {
            System.out.println(threadName + ": before wait");
            nt2.wait();
            System.out.println(threadName + ": after wait");
        }
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
运行结果:
main: before wait
nt: before notify
nt: after notify
main: after wait
==================================
nt2: before notify
nt2: after notify
main: before wait
2
3
4
5
6
7
8
在 nt2 那部分测试中,main: after wait 不会被打印,因为 main 线程调用 wait 之后,没有其他线程调用 notify 去唤醒 main 线程了,因此程序无法继续执行。
# LockSupport.park() 和 Object.wait() 的区别
- wait 需要在 synchronized 中执行,而 park 可以在任何地方执行;
- wait 声明抛出 InterruptedException 异常,需要调用者处理或者抛出,而 park 没有声明抛出异常;
- wait 阻塞的线程,需要通过 notify 来唤醒,而 park 阻塞的线程除了 unpark ,还有其他的唤醒方式;
- wait - notify 二者的调用顺序不能互调,但是 park - unpark 的调用顺序可以互调;
- wait 会释放锁资源,而 park 不会释放锁资源。
# 参考文章
- https://pdai.tech/md/java/thread/java-thread-x-lock-LockSupport.html
- https://www.cnblogs.com/leesf456/p/5347293.html
- https://blog.csdn.net/tangtong1/article/details/102829724
